package com.theaf.core.shiro;

import com.theaf.core.api.model.UserInfo;
import com.theaf.core.constant.ParamNameConstant;
import com.theaf.core.utils.HttpHelper;
import com.theaf.core.utils.LoginUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Scope;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.DelegatingFilterProxy;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.Serializable;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@Component
@Slf4j
public class ShiroConfig {

	@Bean
	@Scope("prototype")
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(
			RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		RedisSerializer keySerializer = new StringRedisSerializer();
		template.setKeySerializer(keySerializer);
		template.setHashKeySerializer(keySerializer);
		template.afterPropertiesSet();
		return template;
	}

	@Bean
	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}

	/**
	 * FilterRegistrationBean
	 * @return
	 */
	@Bean
	public FilterRegistrationBean filterRegistrationBean() {
		FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
		filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
		filterRegistration.setEnabled(true);
		filterRegistration.addUrlPatterns("/*");
		filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
		return filterRegistration;
	}

	/**
	 * 权限管理器
	 * 
	 * @return
	 */
	@Bean(name = "securityManager")
	public DefaultWebSecurityManager securityManager(@Qualifier("sessionManager")SessionManager sessionManager,
													 @Qualifier("userRealm")UserRealm userRealm,
													 @Qualifier("shrioRedisCacheManager")ShrioRedisCacheManager shrioRedisCacheManager) {
		DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
		// 数据库认证的实现
		manager.setRealm(userRealm);
		manager.setCacheManager(shrioRedisCacheManager);
		// session 管理器
		manager.setSessionManager(sessionManager);
		return manager;
	}

	@Bean
	public SessionManager sessionManager(@Qualifier("redisSessionDAO")RedisSessionDAO redisSessionDAO) {
		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(){
			@Override
			protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
				// 如果参数中包含“__sid”参数，则使用此sid会话。 例如：http://localhost/project?__sid=xxx&__cookie=true
				// 其实这里还可以使用如下参数：cookie中的session名称：如：JSESSIONID=xxx,路径中的 ;JESSIONID=xxx，但建议还是使用 __sid参数。
				String sid = ((HttpServletRequest)request).getHeader(ParamNameConstant.TOKEN);
				if (StringUtils.isNotBlank(sid)) {
					// 是否将sid保存到cookie，浏览器模式下使用此参数。
					// 设置当前session状态
					request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
							ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); // session来源与url
					request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sid);
					request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
					return sid;
				}else{
					return super.getSessionId(request, response);
				}
			}
		};
		sessionManager.setSessionDAO(redisSessionDAO);
		sessionManager.setGlobalSessionTimeout(redisSessionDAO.getExpireTime());
		sessionManager.setDeleteInvalidSessions(true);
		sessionManager.setSessionValidationSchedulerEnabled(true);// 是否定时检查session
		return sessionManager;
	}

	/**
	 * @see UserRealm --->AuthorizingRealm
	 * @return
	 */
	@Bean
	@DependsOn(value = { "lifecycleBeanPostProcessor"})
	public UserRealm userRealm() {
		UserRealm userRealm = new UserRealm();
		userRealm.setCachingEnabled(false);
		userRealm.setAuthenticationCachingEnabled(false);//禁用认证缓存
		userRealm.setAuthorizationCachingEnabled(false);
		return userRealm;
	}

	/**
	 * 权限判断过滤器
	 */
	public PermissionsAuthorizationFilter urlPermissionsFilter = new HttpMethodPermissionFilter(){
			@Override
			public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
				throws IOException {
				HttpServletRequest req = (HttpServletRequest) request;
				boolean have = false;
				for(String n:LoginUtils.getFunAathUrl()){
					n = n.replaceAll("\\{\\S+\\}","\\\\S+");
					if(req.getRequestURI().matches(n)){
						have = true;
					}
				}
				if(!have){
					boolean isDef = false;//标识此地址是否需要权限控制
					for(String  n: LoginUtils.getAllFunUrl()){
						n = n.replaceAll("\\{\\S+\\}","\\\\S+");
						if(req.getRequestURI().matches(n)){
							isDef = true;
						}
					}
					if(isDef) {
						return false;
					}
				}
				//不在同一台机构上操作
				Subject subject = SecurityUtils.getSubject();
				UserInfo ui = (UserInfo)subject.getPrincipal();
				if(ui!=null&&!ui.getIp().equals(HttpHelper.getIp(req))){
					return false;
				}
				return super.isAccessAllowed(request,response,mappedValue);
		}

		/**
		 * 获取当前URL+Parameter
		 * @author songwenke
		 * @since 2017年11月19日 下午3:09:26
		 * @param request 拦截请求request
		 * @return 返回完整URL
		 */
		private String getRequestUrl(ServletRequest request) {
			HttpServletRequest req = (HttpServletRequest) request;
			String queryString = req.getQueryString();
			queryString = StringUtils.isBlank(queryString) ? "" : "?" + queryString;
			return req.getRequestURI() + queryString;
		}
	};

	@Bean(name = "shiroFilter")
	public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager,
											  @Value("${smyw.authorityAnonUrls}") String authorityAnonUrls) {
		ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
		bean.setSecurityManager(securityManager);
		bean.setUnauthorizedUrl("/noPopdom");//没权限
		bean.setLoginUrl("/noAuthent");//未认证
		Map<String, Filter> filters = new HashMap<String, Filter>();
		filters.put("perms", urlPermissionsFilter);
		filters.put("anon", new AnonymousFilter());
		bean.setFilters(filters);
		Map<String, String> chains = new LinkedHashMap<String, String>();
		chains.put("/druid", "anon");
		chains.put("/druid/**", "anon");
		chains.put("/swagger**" , "anon");
		chains.put("/webjars**/**/*" , "anon");
		chains.put("/v2/**" , "anon");
		chains.put("/swagger-resources/**/*" , "anon");
		if(authorityAnonUrls!=null){
			for(String s:authorityAnonUrls.split(",")){
				chains.put(s, "anon");
			}
		}
		chains.put("/**", "authc,perms");
		bean.setFilterChainDefinitionMap(chains);
		return bean;
	}

}